package com.data.hijack.protocol.executor;

import java.io.IOException;
import java.nio.channels.SelectionKey;
import java.nio.channels.SocketChannel;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;

import org.apache.log4j.Logger;
import org.jsoup.Connection;

import com.data.hijack.constant.Constants;
import com.data.hijack.context.HolderContext;
import com.data.hijack.http.ContentType;
import com.data.hijack.http.HttpConstants;
import com.data.hijack.http.HttpSession;
import com.data.hijack.http.Method;
import com.data.hijack.util.*;

import cn.hutool.json.JSONObject;
import cn.hutool.json.JSONUtil;

/**
 * @author husq
 */
public class HttpExecutor extends AbstractSchemaExecutor {
    private static final Logger logger = Logger.getLogger(HttpExecutor.class);

    private SocketChannel channel;

    @Override
    public void execute(SelectionKey selectionKey) throws Exception {
        this.channel = (SocketChannel)selectionKey.channel();

        HttpSession hs = this.getHttpSession(this.channel);
        if (Objects.isNull(hs)) {
            this.closeChannel(this.channel);
            return;
        }

        Method method = hs.getMethod();
        ContentType contentType = hs.getContentType();
        String msg = "", omsg = "";
        Map<String, List<String>> formMap = new HashMap<>();;
        Map<String, byte[]> fileMap = new HashMap<>();;
        if (Objects.equals(method, Method.POST)) {
            final Map<String, String> data = new HashMap<>(16);
            hs.parseBody(data);
            if (contentType.isMultipart()) {
                this.parseMultipartFormData(data, hs, formMap, fileMap);
                msg = JSONUtil.parseObj(formMap).toJSONString(0);
                omsg = msg;
            } else if (HttpConstants.POST_FORM_FORMAT.equalsIgnoreCase(contentType.getContentType())) {
                Map<String, List<String>> formParams = hs.getParameters();
                formParams.forEach((k, v) -> {
                    formMap.put(k, v);
                });
                msg = JSONUtil.parseObj(formParams).toJSONString(0);
                omsg = hs.getQueryParameterString();
            } else {
                String raw = data.get(HttpConstants.HTTP_POST_DATA_KEY);
                if (StringUtils.isNotBlank(raw)) {
                    msg = raw;
                    omsg = msg;
                }
            }
        } else if (Objects.equals(method, Method.GET)) {
            Map<String, List<String>> queryParams = hs.getParameters();
            msg = hs.getQueryParameterString();
            if (Constants.MSG_TYPE_JSON_DEF.equalsIgnoreCase(this.getSysConfig().getMsgType())) {
                msg = JSONUtil.parseObj(queryParams).toJSONString(0);
            }
            omsg = hs.getQueryParameterString();
        } else {
            Asserts.check(false, "System exception.");
        }

        String sendedMsg = "";
        if (Constants.IS_INTERCEPTABLE_1.equals(this.getSysConfig().getIsInterceptable())) {
            sendedMsg = omsg;
        } else if (Constants.IS_INTERCEPTABLE_0.equals(this.getSysConfig().getIsInterceptable())) {
            sendedMsg = this.fire(msg);
            JSONObject jsonObj = JSONUtil.parseObj(sendedMsg);
            if (Objects.equals(method, Method.POST)) {
                if (contentType.isMultipart()
                    || HttpConstants.POST_FORM_FORMAT.equalsIgnoreCase(contentType.getContentType())) {
                    jsonObj.forEach((k, v) -> {
                        List<String> list = formMap.get(k);
                        if (list != null) {
                            formMap.replace(k, (List<String>)v);
                        }
                    });
                }
            } else if (Objects.equals(method, Method.GET)) {
                StringBuilder queryStr = new StringBuilder();
                jsonObj.forEach((k, v) -> {
                    if (v instanceof String) {
                        queryStr.append(k).append("=").append(v).append("&");
                    } else if (v instanceof List) {
                        List list = (List)v;
                        list.forEach((lv) -> {
                            queryStr.append(k).append("=").append(lv).append("&");
                        });
                    }
                });
                if (queryStr.length() > 0) {
                    queryStr.deleteCharAt(queryStr.length() - 1);
                }
                sendedMsg = queryStr.toString();
            }
        } else {
            Asserts.check(false, "Configuration error.");
        }

        Connection.Response response = AppUtils.sendByHttp(sendedMsg, this.getSysConfig(), hs, formMap, fileMap);
        AppUtils.writeRespByHttp(this.channel, response, this.getSysConfig().getCharset());

        this.closeChannel(this.channel);
    }

    private HttpSession getHttpSession(SocketChannel channel) throws Exception {
        HttpSession hs =
            AppUtils.getHttpSession(channel, this.getSysConfig().getCharset(), this.getSysConfig().getTmpDir());
        if (Objects.isNull(hs)) {
            return null;
        }

        HolderContext.putOnlyPair(Constants.HTTP_REQUEST_PATH, hs.getUri());

        return hs;
    }

    private void parseMultipartFormData(Map<String, String> reqData, HttpSession hs, Map<String, List<String>> formMap,
        Map<String, byte[]> fileMap) {
        Map<String, List<String>> formParams = hs.getParameters();
        formParams.forEach((k, v) -> {
            formMap.put(k, v);
        });

        reqData.forEach((k, v) -> {
            byte[] fileByte = new byte[0];
            try {
                fileByte = FileUtils.readFromFile(v);
            } catch (IOException e) {
                logger.error(TextUtils.format(e, "%s[%s]", "Error reading file content.", v));
                throw new RuntimeException("Read request data error");
            }
            fileMap.put(k, fileByte);
        });
    }
}
